package demo2

import (
	"fmt"
	"sort"
)

/*
	map 数据类型
		1. map 是引用类型
		2. 在声明的时候不需要知道 map 的长度，map 是可以动态增长的。未初始化的 map 的值是 nil。
		3. key 可以是任意可以用 == 或者 != 操作符比较的类型，比如 string、int、float。
			 所以数组、切片和结构体不能作为 key，但是指针和接口类型可以。
		4. 如果要用结构体作为 key 可以提供 Key() 和 Hash() 方法，这样可以通过结构体的域计算出唯一的数字或者字符串的 key。
		5. value 可以是任意类型的；通过使用空接口类型，我们可以存储任意值，但是使用这种类型作为值时需要先做一次类型断言。
		6. map 传递给函数的代价很小：在 32 位机器上占 4 个字节，64 位机器上占 8 个字节，无论实际上存储了多少数据。
			 通过 key 在 map 中寻找值是很快的，比线性查找快得多，但是仍然比从数组和切片的索引中直接读取要慢 100 倍；
			 所以如果很在乎性能的话还是建议用切片来解决问题。
		7. map 也可以用函数作为自己的值，这样就可以用来做分支结构：key 用来选择要执行的函数。
		8. 常用的 len(map1) 方法可以获得 map 中的 pair 数目，这个数目是可以伸缩的，因为 map-pairs 在运行时可以动态添加和删除。
*/

// TestDemo27 map 映射
func TestDemo27() {
	// 声明： var map1 map[keytype]valuetype
	var mapLt map[string]int
	var mapCreated map[string]float32
	var mapAssigned map[string]int
	mapLt = map[string]int{"one": 1, "two": 2}
	// map 是引用类型的： 内存用 make 方法来分配。
	mapCreated = make(map[string]float32)
	// 引用类型
	mapAssigned = mapLt
	mapCreated["key1"] = 4.5
	mapCreated["key2"] = 3.14159
	mapAssigned["two"] = 3
	fmt.Printf("Map 1 at \"one\" is: %d\n", mapLt["one"])
	fmt.Printf("Map created at \"key2\" is: %f\n", mapCreated["key2"])
	fmt.Printf("Map assigned at \"two\" is: %d\n", mapAssigned["two"])
	fmt.Printf("Map 1 at \"two\" is: %d\n", mapLt["two"])
	fmt.Printf("Map 1 at \"ten\" is: %d\n", mapLt["ten"])
	fmt.Printf("Map 1 pair length: %d\n", len(mapLt))

	// map 是引用类型的： 内存用 make 方法来分配。
	// map 的初始化: var map1[keytype]valuetype = make(map[keytype]valuetype)
	//    简化为： map1 := make(map[keytype]valuetype)
	map1 := make(map[string]int)
	// map2 := make(map[string]int{})
	// 不要使用 new，永远用 make 来构造 map
	fmt.Println(map1)
	fmt.Println(mapLt)
	fmt.Println(mapAssigned)
	fmt.Println(mapCreated)

	// map 可以根据新增的 key-value 对动态的伸缩，因此它不存在固定长度或者最大限制。但是也可以选择标明 map 的初始容量 capacity
	//   格式： make(map[keytype]valuetype, cap) 。
	map2 := make(map[string]float32, 100)
	// 当 map 增长到容量上限的时候，如果再增加新的 key-value 对，map 的大小会自动加 1。
	// 所以出于性能的考虑，对于大的 map 或者会快速扩张的 map，即使只是大概知道容量，也最好先标明。
	fmt.Println(map2)

	// slice 作为 map 的值
	map3 := make(map[int][]int)
	map4 := make(map[int]*[]int)

	map3[1] = []int{10, 20, 40}
	slice1 := []int{10, 20, 30}
	// map4 的值是：[]int slice 的指针
	map4[1] = &slice1

	fmt.Println(map3)
	fmt.Println(map4)

	// &符号的意思是对变量取地址，如：变量a的地址是&a
	// *符号的意思是对指针取值，如:*&a，就是a变量所在地址的值，当然也就是a的值了
	var aa = 1
	var bb = &aa
	var cc = &bb
	var dd = *bb
	// var aa int = 1
	// var bb *int = &aa
	// var cc **int = &bb
	// var dd int = *bb
	fmt.Println(aa)
	fmt.Println(bb)
	fmt.Println(cc)
	fmt.Println(dd)

	/*
			Map 的遍历: 访问map中的每一个键值对
		  通过 for range 遍历 Map, 和遍历 array, slice 一样
	*/
	rangeMap1 := make(map[string]int)

	rangeMap1["price"] = 100
	rangeMap1["amount"] = 10
	rangeMap1["no"] = 4

	for k, v := range rangeMap1 {
		fmt.Println(k, v)
	}

	for _, v := range rangeMap1 {
		fmt.Println(v)
	}

	for k := range rangeMap1 {
		fmt.Println(k)
	}

	// tips: 遍历输出元素的顺序与填充顺序无关。不能期望 map 在遍历时返回某种期望顺序的结果。
	// 如果需要特定顺序的遍历结果，正确的做法是排序：
	// 1. 声明切片保存 map keys
	mapSlice := make([]string, 3, 3)

	// 2. 遍历 map, 保存 keys 到 slice
	for k := range rangeMap1 {
		mapSlice = append(mapSlice, k)
	}

	// 对切片进行排序
	sort.Strings(mapSlice)

	// 输出 slice
	fmt.Println(mapSlice)

	/*
		map元素的删除和清空
			1. 内置函数 delete()，用于删除容器内的元素
			  delete() 函数的格式如下： delete(map, 键)
	*/
	rangeMap2 := make(map[string]int)

	rangeMap2["price"] = 100
	rangeMap2["amount"] = 10
	rangeMap2["no"] = 4

	fmt.Println("删除前", rangeMap2)
	delete(rangeMap2, "no")
	fmt.Println("删除后", rangeMap2)

	// Go 语言中并没有为 map 提供任何清空所有元素的函数、方法。
	// 清空 map 的唯一办法就是重新 make 一个新的 map。
	// 不用担心垃圾回收的效率，Go 语言中的并行垃圾回收效率比写一个清空函数高效多了。
	rangeMap2 = make(map[string]int)
	fmt.Println(rangeMap2)
}
